#include <iostream>
#include <string>
#include <vector>
#include <boost/filesystem.hpp>
#include "util.hpp"
#include "log.hpp"

/*
    函数形参用途说明：
    const & : 输入型参数
    * : 输出型参数
    & : 输入输出型参数
*/

using std::string;
using std::vector;
using std::cout;
using std::endl;
using std::cerr;

const string src_path = "data/raw_html"; // 存放原始HTML文件的目录路径
const string out_file = "data/clean_html/data.txt"; // 存放去标签与清洗后的HTML数据文件路径

/* 存放去标签后HTML元素结构体 */
typedef struct document_elem
{
    string title; // 文档标题
    string content; // 文档内容
    string url; // 文档在网站中URL
}doc_elem;


bool EnumFile(const string& src_path,vector<string>* files_list); // 递归将src_path下所有HTML文件的路径名保存到files_list中
bool ParseHtml(const vector<string>& files_list,vector<doc_elem>* docs_list); // 遍历files_list，解析HTML文件内容构建doc_elem类型元素添加到docs_list指向vector
bool SaveHtml(const vector<doc_elem>& docs_list,const string& out_file); // 将解析后的各个HTML文件内容以特定格式写入out_file文件中

int main()
{
    vector<string> files_list;
    if(!EnumFile(src_path,&files_list))
    {
        LOG(ERROR,"enum file fail");
        //cerr << "enum file fail" << endl;

        return 1;
    }

    vector<doc_elem> docs_list;
    if(!ParseHtml(files_list,&docs_list))
    {
        LOG(ERROR,"ParseHtml fail");
        //cerr << "ParseHtml fail" << endl;
        
        return 2;
    }

    if(!SaveHtml(docs_list,out_file))
    {
        LOG(ERROR,"SaveHtml fail");
        //cerr << "SaveHtml fail" << endl;

        return 3;
    }

    return 0;
}

bool EnumFile(const string& src_path,vector<string>* files_list)
{
    namespace alias_fs = boost::filesystem; // 命名空间别名，简化嵌套namespace

    alias_fs::path root_path(src_path);
    
    /* 判断路径是否存在 */
    if(!alias_fs::exists(root_path))
    {
        string log_message(src_path);
        log_message += " not exists";
        LOG(ERROR,log_message);
        
        return false;
    }

    /* 
        递归root_path下所有文件，文件分三种情况
        1. 不是普通文件，continue
        2. 不是以.html结尾的普通文件,continue
        3. 以.html结尾的普通文件,将文件路径保存到file_list指针指向的vector
    */
    alias_fs::recursive_directory_iterator end;
    for(alias_fs::recursive_directory_iterator elem(root_path);elem != end;elem++)
    {
        if(!alias_fs::is_regular_file(*elem))
        {
            continue;
        }

        if(elem->path().extension() != ".html")
        {
            continue;
        }

        files_list->push_back(elem->path().string());
    }

    return true;
}

static bool ParseTitle(const string& file_content,string* title)
{
    /*
        查找<title> 起始下标beg_pos、</title>起始下标end_pos
        通过beg_pos加上"<title>"大小找到内容起始位置
    */
    size_t beg_pos = file_content.find("<title>");
    if(beg_pos == string::npos)
    {
        return false;
    }

    size_t end_pos = file_content.find("</title>");
    if(end_pos == string::npos)
    {
        return false;
    }

    beg_pos += string("<title>").size(); // 找到内容起始位置
    if(beg_pos >= end_pos)
    {
        return false;
    }

    *title = file_content.substr(beg_pos,end_pos - beg_pos);

    return true;
}

static bool ParseContent(const string& file_content,string* content)
{
    /* 状态机，当指向字符处于标签数据位置时为LABLE，处于正文数据位置时为CONTENT */
    enum status
    {
        LABLE,
        CONTENT
    };

    enum status s = LABLE;
    for(char ch : file_content)
    {
        switch(s)
        {
            case LABLE:
                if('>' == ch) // 准备进入正文数据位置
                {
                    s = CONTENT;
                }
                break;
            case CONTENT:
                if('<' == ch) // 准备进入标签数据位置
                {
                    s = LABLE;
                }
                else if('\n' == ch)
                {
                    ch = ' ';
                    content->push_back(ch);
                }
                else
                {
                    content->push_back(ch);
                }
                break;
            default:
                break;
        }
    }

    return true;
}

static bool ParseUrl(const string& file_path,string* url)
{
    /*
        boost URL示例： https://www.boost.org/doc/libs/1_80_0/doc/html/accumulators.html
        原始 url_tail = src_path + file_path 
        将head_url + file_path拼接成URL
    */
    string url_head = "https://www.boost.org/doc/libs/1_80_0/doc/html";
    string url_tail = file_path.substr(src_path.size());

    *url = url_head + url_tail;

    return true;
}

bool ParseHtml(const vector<string>& files_list,vector<doc_elem>* docs_list)
{
    /*
        遍历files_list，解析HTML文件内容构建doc_elem类型元素添加到docs_list指向vector
        (1) 读取文件内容,失败continue 
        (2) 解析文件内容中的title,失败continue 
        (3) 解析文件内容中正文数据,失败continue
        (4) 构建URL,失败continue  
    */
    for(const string& file_path : files_list)
    {
        string file_content; // 文件内容
        if(!ns_util::file_util::ReadFile(file_path,&file_content))
        {
            continue;
        }

        doc_elem elem;

        if(!ParseTitle(file_content,&elem.title))
        {
            continue;
        }

        if(!ParseContent(file_content,&elem.content))
        {
            continue;
        }

        if(!ParseUrl(file_path,&elem.url))
        {
            continue;
        }

        /*  
             直接docs_list->push_back(elem)会调用拷贝构造
             move允许直接转移对象的资产和属性的所有权，而不必在实参为右值时复制它们
         */
        docs_list->push_back(std::move(elem)); 
    }

    return true;
}

bool SaveHtml(const vector<doc_elem>& docs_list,const string& out_file)
{
    /*
        以二进制方式将docs_list中的每个doc_elem写入 out_file文件中
        doc_elem内元素之间通过\3分隔
        doc_elem之间通过\n分隔
        格式：title\3content\3url\ntitle\3content\3url\n
    */
    #define SEPARATOR '\3' // 元素之间分隔符

    std::ofstream out(out_file,std::ios::out | std::ios::binary);

    if(!out.is_open())
    {
        string log_message("open ");
        log_message += out_file;
        log_message += " fail";
        LOG(ERROR,log_message);    

        //cerr << "open " << out_file << "fail " << endl;
        
        return false;
    }

    for(auto& elem : docs_list)
    {
        string doc_string;
        doc_string += elem.title;
        doc_string += SEPARATOR;
        doc_string += elem.content;
        doc_string += SEPARATOR;
        doc_string += elem.url;
        doc_string += '\n';

        out.write(doc_string.c_str(),doc_string.size());
    }

    out.close();

    return true;
}
